package com.xyhy.starter.mfx.repostory;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.constant.SqlOperator;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.query.QueryTable;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.update.UpdateWrapper;
import com.mybatisflex.core.util.UpdateEntity;
import com.xyhy.core.base.ToolConstant;
import com.xyhy.core.base.exception.ServiceException;
import com.xyhy.core.utils.Asserts;
import com.xyhy.core.web.model.page.PageResult;
import com.xyhy.core.web.model.page.PurePageQO;
import com.xyhy.starter.mfx.entity.IId;
import com.xyhy.starter.mfx.utils.PageUtil;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

@Getter
public abstract class MfxRepositoryImpl<T extends IId<ID>, ID extends Serializable, TABLE extends QueryTable,
        MAPPER extends BaseMapper<T>> implements MfxRepository<T, ID> {
  protected @Getter TABLE table;
  protected @Getter QueryColumn pkColumn;
  protected @Autowired MAPPER mapper;

  @PostConstruct
  private void init() {
    table = getTableInstance();
    pkColumn = getPKColumn();
  }

  @Override
  public T save(T record) {
    if (Objects.isNull(record)) {
      return null;
    }
    if (Objects.isNull(record.getId())) {
      return insert(record);
    }

    T existRecord = mapper.selectOneById(record.getId());
    if (Objects.isNull(existRecord)) {
      return insert(record);
    } else {
      return updateById(record);
    }
  }

  @Override
  public T insert(T record) {
    return doInsert(record, false);
  }

  @Override
  public T insertSelective(T record) {
    return doInsert(record, true);
  }

  @Override
  public List<T> insertMultiple(List<T> records) {
    if (CollUtil.isEmpty(records)) return Collections.emptyList();

    records.forEach(this::fillBeforeInsert);
    mapper.insertBatch(records);
    return CollUtil.newArrayList(records);
  }

  @Override
  public boolean deleteById(ID id) {
    if (Objects.isNull(id)) {
      return false;
    }
    return mapper.deleteById(id) > 0;
  }

  @Override
  public int deleteByIdIn(Collection<ID> ids) {
    List<ID> idList = CollUtil.removeNull(CollUtil.distinct(ids));
    if (CollUtil.isEmpty(idList)) return NumberUtils.INTEGER_ZERO;

    return mapper.deleteBatchByIds(idList);
  }

  @Override
  public int deleteByColumn(QueryColumn column, Object value) {
    if (ObjectUtil.hasNull(column, value)) return NumberUtils.INTEGER_ZERO;
    return mapper.deleteByCondition(column.eq(value));
  }

  @Override
  public int deleteByColumnIn(QueryColumn column, List<?> values) {
    List<?> valueList = CollUtil.removeNull(CollUtil.distinct(values));
    if (CollUtil.isEmpty(valueList)) return NumberUtils.INTEGER_ZERO;
    return mapper.deleteByCondition(column.in(values));
  }

  @Override
  public List<T> findAll() {
    return mapper.selectAll();
  }

  @Override
  public List<ID> findAllId() {
    QueryWrapper qw = QueryWrapper.create().select(pkColumn);
    return mapper.selectListByQueryAs(qw, getIdType());
  }

  @Override
  public List<T> findAllByIdInOrder(List<ID> ids) {
    return findAllById(ids).stream().sorted(Comparator.comparing(x -> ids.indexOf(x.getId()))).collect(Collectors.toList());
  }

  @Override
  public List<T> findAllById(Collection<ID> ids) {
    List<ID> idList = CollUtil.removeNull(CollUtil.distinct(ids));
    if (CollUtil.isEmpty(idList)) return Collections.emptyList();

    return mapper.selectListByIds(idList);
  }

  @Override
  public PageResult<T> findPage(PurePageQO qo, QueryWrapper queryWrapper) {
    return PageUtil.responseOf(mapper.paginate(PageUtil.of(qo), queryWrapper));
  }

  @Override
  public T findValidOne(ID id) {
    return findById(id).orElseThrow(() -> {
      Type entityType = getEntityType();
      String idStr = Optional.ofNullable(id).map(Object::toString).orElse("null");
      String message = String.format("%s数据不存在或已删除：id[%s]", entityType.getTypeName(), idStr);
      return new ServiceException(message);
    });
  }

  @Override
  public T findByIdOrNull(ID id) {
    return findById(id).orElse(null);
  }

  @Override
  public Optional<T> findById(ID id) {
    if (Objects.isNull(id)) {
      return Optional.empty();
    }
    return Optional.ofNullable(mapper.selectOneById(id));
  }

  @Override
  public boolean existById(ID id) {
    if (Objects.isNull(id)) return false;
    return mapper.selectCountByCondition(pkColumn.eq(id)) > 0L;
  }

  @Override
  public boolean existByColumn(QueryColumn column, Object value) {
    return existByColumn(column, value, null);
  }

  @Override
  public boolean existByColumn(QueryColumn column, Object value, ID curId) {
    QueryWrapper qw = new QueryWrapper().select(pkColumn).from(table)
            .where(QueryCondition.create(column, value));
    if (Objects.nonNull(curId)) {
      qw.and(pkColumn.ne(curId));
    }
    qw.limit(NumberUtils.INTEGER_ONE);
    return CollUtil.isNotEmpty(mapper.selectListByQuery(qw));
  }

  @Override
  public boolean existByColumnIn(QueryColumn column, List<?> values) {
    return existByColumnIn(column, values, null);
  }

  @Override
  public boolean existByColumnIn(QueryColumn column, List<?> values, ID curId) {
    if (CollUtil.isEmpty(values)) return false;
    QueryWrapper qw = new QueryWrapper().select(pkColumn).from(table)
            .where(QueryCondition.create(column, SqlOperator.IN, values));
    if (Objects.nonNull(curId)) {
      qw.and(pkColumn.ne(curId));
    }
    qw.limit(NumberUtils.INTEGER_ONE);
    return CollUtil.isNotEmpty(mapper.selectListByQuery(qw));
  }

  @Override
  public List<ID> existByIdIn(List<ID> ids) {
    List<ID> idList = CollUtil.removeNull(CollUtil.distinct(ids));
    if (CollUtil.isEmpty(idList)) return Collections.emptyList();
    QueryWrapper qw = new QueryWrapper().select(pkColumn).from(table).where(pkColumn.in(idList));

    return mapper.selectObjectListByQueryAs(qw, getIdType());
  }

  @Override
  public T updateById(T record) {
    return doUpdateById(record, false);
  }

  @Override
  public T updateByIdSelective(T record) {
    return doUpdateById(record, true);
  }

  @Override
  public <DTO> boolean updateByIdPart(DTO dto) {
    ID id = (ID) ReflectUtil.getFieldValue(dto, ToolConstant.COLUMN.ID);
    return updateByIdPart(id, dto);
  }

  @Override
  public <DTO> boolean updateByIdPart(ID id, DTO dto) {
    Asserts.notNull(id, "主键为空，更新失败!");
    Asserts.notNull(dto, "dto不能为空，更新失败!");

    Map<String, Field> dtoFieldMap = ReflectUtil.getFieldMap(dto.getClass());

    Class tClass = (Class) getEntityType();
    T entity = (T) UpdateEntity.of(tClass, id);

    Field[] tableFields = ReflectUtil.getFields((Class) getTableType());
    List<String> entityFields = Arrays.stream(tableFields).filter(x -> x.getType().equals(QueryColumn.class))
            .map(x -> x.getName()).map(x -> StrUtil.toCamelCase(x.toLowerCase())).collect(Collectors.toList());

    UpdateWrapper<T> wrapper = UpdateWrapper.of(entity);
    dtoFieldMap.entrySet().forEach(x -> {
      if (!entityFields.contains(x.getKey())) return;
      if (ToolConstant.COLUMN.ID.equals(x.getKey())) return;
      Object fieldValue = ReflectUtil.getFieldValue(dto, x.getValue());
      wrapper.set(x.getKey(), fieldValue);
    });

    return mapper.update(entity) > 0;
  }

  @Override
  public <R> boolean updateColumnById(ID id, QueryColumn column, R value) {
    if (ObjectUtil.hasNull(id, column)) return false;

    Class tClass = (Class) getEntityType();
    T entity = (T) UpdateEntity.of(tClass, id);

    UpdateWrapper<T> wrapper = UpdateWrapper.of(entity);
    wrapper.set(column, value);

    return mapper.update(entity) > 0;
  }

  @Override
  public int updateColumnsNullById(ID id, QueryColumn... columns) {
    List<QueryColumn> columnList = CollUtil.toList(columns);
    if (Objects.isNull(id) || CollUtil.isEmpty(columnList)) return 0;

    Class tClass = (Class) getEntityType();
    T entity = (T) UpdateEntity.of(tClass, id);

    UpdateWrapper<T> wrapper = UpdateWrapper.of(entity);
    columnList.forEach(x -> wrapper.set(x, null));

    return mapper.update(entity);
  }

  protected abstract Class<ID> getIdType();

  protected abstract void fillBeforeInsert(T record);


  protected T doInsert(T record, boolean ignoreNull) {
    if (Objects.isNull(record)) {
      return null;
    }
    fillBeforeInsert(record);
    if (ignoreNull) {
      mapper.insertSelective(record);
    } else {
      mapper.insert(record);
    }
    return record;
  }

  protected QueryColumn getPKColumn() {
    return (QueryColumn) ReflectUtil.getFieldValue(table, "ID");
  }

  protected TABLE getTableInstance() {
    return ReflectUtil.newInstance(getTableType().getTypeName());
  }

  protected Type getTableType() {
    return TypeUtil.getTypeMap(this.getClass()).entrySet().stream()
            .filter(x -> x.getKey().toString().equals("TABLE"))
            .map(x -> x.getValue()).findFirst()
            .orElseThrow(() -> new ServiceException("获取 Table 失败!"));
  }


  private T doUpdateById(T record, boolean selective) {
    if (Objects.isNull(record)) return null;
    Assert.notNull(record.getId(), "主键为空，无法更新!");

    mapper.update(record, selective);
    return record;
  }

  private Type getEntityType() {
    return TypeUtil.getTypeMap(this.getClass()).entrySet().stream()
            .filter(x -> x.getKey().toString().equals("T")).map(Map.Entry::getValue)
            .findFirst().orElseThrow(() -> new ServiceException("获取实体泛型失败!"));
  }

}
